<?php

namespace app\api\service;

use app\order\model\ImsEweiShopOrder;
use app\wechat\model\WechatPaymentRecord;
use app\wechat\model\WechatPaymentRefund;
use app\wechat\service\WechatService;
use EasyWeChat\MiniApp\Application;
use think\admin\Exception;
use think\admin\extend\CodeExtend;
use think\facade\Cache;
use think\facade\Config;

/**
 * 退款服务
 */
class B2bRefundService
{
    /**
     * 退款请求地址
     */
    private const WECHAT_ORDER_REFUND_API_URL='https://api.weixin.qq.com/retail/B2b/refund';
    /**
     * 获取订单详情页面
     */
    private const WECHAT_B2B_ORDER_RETAIL='https://api.weixin.qq.com/retail/B2b/getorder';
    /**
     * 查询退款订单状态
     */
    private const WECHAT_B2B_ORDER_REFUND='https://api.weixin.qq.com/retail/B2b/getrefund';

    private const REFUND_BY_HUMAN_CUSTOMER_SERVICE=1;//人工客服退款
    private const THE_USER_REFUNDS_BY_THEMSELVES=2;//用户自己退款
    private const REFUND_FROM_OTHER=3;//其他

    /**
     * 退款来源，枚举值 1：人工客服退款 2：用户自己退款 3：其他
     * 示例值：1
     */

    /**
     * @return array
     * 请求参数对应的名称
     */
    private function queryReFundParamMap()
    {
        $data['mchid']              ='微信商户号';
        $data['out_trade_no']       ='商户订单号';
        $data['order_id']           ='B2b支付订单号';
        $data['out_refund_no']      ='商户退款单号';
        $data['refund_amount']      ='退款金额';
        $data['refund_from']        ='退款来源';
        $data['refund_reason']      ='退款原因';
        return $data;
    }

    /**
     * 创建支付单退款
     * @param string $pcode 支付单号
     * @param string $amount 退款金额
     * @param string $reason 退款原因
     * @return array
     * @throws \WeChat\Exceptions\InvalidResponseException
     * @throws \WeChat\Exceptions\LocalCacheException
     * @throws \think\admin\Exception
     */
    public  function refund(string $pcode, string $amount, string $reason = ''): array
    {

        error_log('refund_start---1:'.$pcode."\t".$amount."\t".$reason);

        // 最近一条记录，同步查询刷新
        // 同步已退款状态
        $record = $this->refundSync($pcode);
        if ($record->getAttr('refund_amount') >= $record->getAttr('payment_amount')) {
            return [1, '该订单已完成退款！'];
        }
        if ($record->getAttr('refund_amount') + floatval($amount) > $record->getAttr('payment_amount')) {
            return [0, '退款大于支付金额！'];
        }
        // 创建支付退款申请
        do $check = ['code' => $rcode = CodeExtend::uniqidNumber(16, 'R')];
        while (($model = WechatPaymentRefund::mk()->master()->where($check)->findOrEmpty())->isExists());
        // 初始化退款申请记录
        $model->save(['code' => $rcode, 'record_code' => $pcode, 'refund_amount' => $amount, 'refund_remark' => $reason]);
        $options['mchid']=$this->queryMerchantId();
        $options['out_trade_no']=$pcode;
        $options['out_refund_no']=$rcode;
        $options['refund_amount']=$amount*100;
        $options['refund_from']=self::REFUND_BY_HUMAN_CUSTOMER_SERVICE;
        $options['refund_reason']=0;
        $result = $this->sendB2bOrderRefundToWeChat($options);
        if ($result['errcode']==0) {
            return $this->refundSyncByQuery($rcode);
        } else {
            return [0, $result['errmsg'] ?? $result['errmsg']];
        }
    }


    /**
     * @param string $pCode
     * @return WechatPaymentRecord
     * @throws Exception
     * @throws \WeChat\Exceptions\InvalidResponseException
     * @throws \WeChat\Exceptions\LocalCacheException
     * 退款同步
     */
    public  function refundSync(string $pCode): WechatPaymentRecord
    {
        $record = WechatPaymentRecord::mk()->where(['code' => $pCode])->findOrEmpty();
        if ($record->isEmpty())
        {
            throw new Exception('支付单不存在！');
        }
        if ($record->getAttr('payment_status') < 1) throw new Exception("支付未完成！");
        // 最近一条记录，同步查询刷新
        $map = ['record_code' => $pCode];
        $last = WechatPaymentRefund::mk()->where($map)->order('id desc')->findOrEmpty();
        error_log('refund_last----2:'.json_encode($last));
        if($last){
            $last=$last->toArray();
            if ($last && isset($last['id']) && $last['refund_status'] === 0) {
                $this->refundSyncByQuery($last['code']);
            }
        }

        // 统计刷新退款金额
        $where = ['record_code' => $pCode, 'refund_status' => 1];
        $amount = WechatPaymentRefund::mk()->where($where)->sum('refund_amount');
        $record->save(['refund_amount' => $amount, 'refund_status' => intval($amount > 0)]);
        error_log('refund_last----3:'.json_encode($amount));
        return $record;
    }

    /**
     * 同步退款状态
     * @param string $rCode
     * @return array
     * @throws \WeChat\Exceptions\InvalidResponseException
     * @throws \WeChat\Exceptions\LocalCacheException
     * @throws \think\admin\Exception
     */
    public  function refundSyncByQuery(string $rCode): array
    {
        $cacheKey="refund_".$rCode;
        if(!Cache::has($cacheKey)){
            Cache::set($cacheKey,0);
        }

        error_log('refund_last----2-1:'.$rCode);

        $refund = WechatPaymentRefund::mk()->where(['code' => $rCode])->findOrEmpty();
        if ($refund->isEmpty()) return [0, '退款不存在！'];
        if ($refund->getAttr('refund_status')) return [1, '退款已完成！'];
        /**
         * 查询退款订单关状态
         */
        $reqNum=Cache::get($cacheKey);
        error_log('refund_last----2----0:'.$reqNum);
        $result=[];
        if($reqNum < 3){
            $result = $this->queryB2bOrderRefundDetail($rCode);

            error_log('refund_last----2-2:'.json_encode($result));

            if($result && isset($result['errcode']) && $result['errcode']==0){
                $extra=[];
                if(isset($result['wxpay_refund_id'])){
                    $extra['refund_trade']    = $result['wxpay_refund_id'];
                }
                if(isset($result['refund_status'])){
                    $extra['refund_scode']    = $result['refund_status'];
                    $extra['refund_status']   = intval($result['refund_status'] === 'REFUND_SUCC');
                }
                $extra['refund_notify']    = json_encode($result, 64 | 256);
                if(isset($result['Refund_channel_info']['user_received_account'])){

                    $extra['refund_account']    = $result['Refund_channel_info']['user_received_account'] ?? '';
                }
                if (isset($result['refund_time'])) {
                    $extra['refund_time'] = $result['refund_time'];
                }
                if($extra){
                    $refund->save($extra);
                }
            }
            $reqNum+=1;
            Cache::set($cacheKey,$reqNum);
            // 同步支付订单
            $this->refundSync($refund->getAttr('record_code'));
            if (isset($result['refund_status']) && $result['refund_status'] === 'REFUND_INIT') return [1, '退款单初始化！'];
            if (isset($result['refund_status']) && $result['refund_status'] === 'REFUND_PROCESSING') return [1, '退款处理中！'];
            if (isset($result['refund_status']) && $result['refund_status'] === 'REFUND_SUCC') return [1, '退款成功'];
            if (isset($result['refund_status']) && $result['refund_status'] === 'REFUND_FAIL') return [1, '退款失败'];
            return [0, $result['errmsg'] ?? $result['errmsg']];
        }
        return [0, "失败了"];
    }


    /**
     * @param $postData
     * @return mixed
     * 发送退款申请
     */
    protected function sendB2bOrderRefundToWeChat($postData)
    {
        $uri='/retail/B2b/refund';
        $post_body=json_encode($postData,JSON_UNESCAPED_UNICODE);
        $app_key = Config::get('wechat.wechat_b2b.app_key');
        $paySig=$this->calcPaySig($uri,$post_body,$app_key);
        $app=$this->queryEasyWeChatApplication();
        $response = $app->getClient()->postJson("retail/B2b/refund?pay_sig=".$paySig, $postData);
        error_log('refund_reqeust-----'.$uri.json_encode($response,JSON_UNESCAPED_UNICODE));
        $result=$response->toArray(false);
        $logData['data']=$postData;
        $logData['post_body']=$post_body;
        $logData['response']=$response;
        error_log('refund------'.$uri.json_encode($logData,JSON_UNESCAPED_UNICODE));
        return $result;
    }

    /**
     * @param $out_trade_no
     * @return void
     * 查询退款订单 状态
     */
    protected function queryB2bOrderRefundDetail($out_trade_no){

        $uri='/retail/B2b/getrefund';
        $data['mchid']=$this->queryMerchantId();
        $data['out_refund_no']=$out_trade_no;
        $post_body=json_encode($data,JSON_UNESCAPED_UNICODE);
        $app_key = Config::get('wechat.wechat_b2b.app_key');
        $paySig=$this->calcPaySig($uri,$post_body,$app_key);
        $app=$this->queryEasyWeChatApplication();
        $response = $app->getClient()->postJson("retail/B2b/getrefund?pay_sig=".$paySig, $data);
        $result=$response->toArray(false);
        $logData['data']=$data;
        $logData['post_body']=$post_body;
        $logData['response']=$response;
        error_log('refund------queryB2bOrderRefundDetail'.$uri.json_encode($logData,JSON_UNESCAPED_UNICODE));
        return $result;
    }



    /**
     * 查询订单信息
     */
    /**
     * @return void
     * https://api.weixin.qq.com/retail/B2b/getorder?access_token=ACCESS_TOKEN&pay_sig=xxxxx
     */
    protected function queryB2bRetailOrder($out_trade_no)
    {

        $uri='/retail/B2b/getorder';
        $data['mchid']=$this->queryMerchantId();
        $data['out_trade_no']=$out_trade_no;
        $post_body=json_encode($data,JSON_UNESCAPED_UNICODE);

        $app_key = Config::get('wechat.wechat_b2b.app_key');
        $paySig=$this->calcPaySig($uri,$post_body,$app_key);

        $orderUrl=$this->queryRequestAccessToken(self::WECHAT_B2B_ORDER_RETAIL,$paySig);
        list($header, $content) = $this->_doRequestCurl('POST', $orderUrl, ['data' => $data, 'header' => []]);
        /**
         *
         */
        return json_decode($content, true);
    }

    /**
     * @param $url
     * @param $paySig
     * @return string
     * 获取请求的url
     */
    private function queryRequestAccessToken($url,$paySig)
    {
        $data['access_token']=$this->queryWeChatAccessToken();
        $data['pay_sig']=$paySig;
        return $url."?".http_build_query($data);
    }

    /**
     * @return void
     * 获取商户ID
     */
    private function queryMerchantId()
    {
        $mchid=Config::get('wechat.wechat_b2b.mch_id');
        return $mchid;

    }

    /**
     * @return mixed
     * 获取Appkey
     */
    private function queryAppKey()
    {
        $app_key = Config::get('wechat.wechat_b2b.app_key');
        return $app_key;
    }

    public function calcPaySig($uri, string $post_body, $app_key): string
    {
        $temp = $uri . '&' . $post_body;
        return hash_hmac('sha256', $temp, $app_key);
    }

    private function _doRequestCurl($method, $location, $options = [])
    {
        $curl = curl_init();
        // POST数据设置
        if (strtolower($method) === 'post') {
            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $options['data']);
        }
        // CURL头信息设置
        if (!empty($options['header'])) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $options['header']);
        }
        curl_setopt($curl, CURLOPT_URL, $location);
        curl_setopt($curl, CURLOPT_HEADER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 60);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        $content = curl_exec($curl);
        $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $logData=[];
        $logData['options']=$options;
        $logData['location']=$location;
        $logData['content']=$content;
        $logData['headerSize']=$headerSize;

        error_log('ORDER_REFUND_ERROR'.json_encode($logData,JSON_UNESCAPED_UNICODE));
        curl_close($curl);
        return [substr($content, 0, $headerSize), substr($content, $headerSize)];
    }


    /**获取操作的AccessToken
     * @return string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     */
    private function queryWeChatAccessToken()
    {

        $app=$this->queryEasyWeChatApplication();
        $accessToken = $app->getAccessToken();
        $access_token = $accessToken->getToken();
        return $access_token;
    }


    private function queryEasyWeChatApplication()
    {
        $appid = Config::get('wechat.wechat_b2b.app_id');
        $app_secret = Config::get('wechat.wechat_b2b.app_secret');
        $config = [
            'app_id' => $appid,
            'secret' => $app_secret,
            'mch_id' => $this->queryMerchantId(),
            'log' => [
                'level' => 'debug',
            ]
        ];
        $app = new Application($config);
        return $app;
    }



}